前面提到瀏覽器送來的要求經過 router 比對之後會送到設定的 controller,接著 controller 從 context 取得資料,並且把資料填入 html template,最後回傳 html 頁面。
我們一步一步的從 controller 開始,把這些零件建立起來。
先使用 mix phx.server
(或 iex -S mix phx.server
) 開啟伺服器,並在網址輸入我們前一篇設定好的 note 列表畫面網址 http://localhost:4000/notes
。
我們馬上看到,因為我們只有設定好 router,卻沒有建立我們在上面寫的 Controller module,所以出現錯誤訊息:
function GratitudeWeb.NoteController.init/1 is undefined (module GratitudeWeb.NoteController is not available)
module GratitudeWeb.NoteController is not available
他找不到這個 module,因為我們還沒有建立他。
建立一個檔案 lib/gratitude_web/controllers/note_controller.ex
,並且加入以下程式碼:
defmodule GratitudeWeb.NoteController do
use GratitudeWeb, :controller
end
這裡我們使用 use GratitudeWeb, :controller
來引入 Phoenix 的 controller 功能。
(如果有興趣的話可以研究一下 use 與 GratitudeWeb 裡面的 controller
是怎麼實作的,但是目前可以暫時把這段當成 Controller 就好。)
現在我們再次開啟網頁,會看到錯誤訊息變成:
function GratitudeWeb.NoteController.index/2 is undefined or private
記得我們在 router 定義的時候是寫(用 resources 替代也是一樣)
get "/notes", NoteController, :index
Phoenix 的 Router 非常直覺,依照上面設定,就會去找 NoteController
裡面的 index
函式。
(題外話:不管是 Elixir 或 Phoenix,會希望寫的明顯直覺一些,盡量不要有隱藏的邏輯,所以這邊不管我們要接的 module 放在哪叫什麼都沒關係,目前看起來雖然有點攏長,感覺需要寫很多,不過這點在實際專案東西多了之後,會發現這樣寫比較好找,也方便修改。)
我們在 NoteController
裡面加入 index
函式:
defmodule GratitudeWeb.NoteController do
use GratitudeWeb, :controller
def index(conn, params) do
end
end
從 router 呼叫來 controller 的時候,會傳入兩個參數,第一個是 conn
,第二個是 params
。
conn 代表目前的連線,params 則是這次要求帶來的參數。
在 Phoenix.Controller module 裡面,有一系列可以在 controller 裡面使用的函式,我們先試試看 html
這一個。
def index(conn, params) do
html(conn, "Hello World")
end
現在我們再次開啟網頁,我們可以看到我們直接印出來的文字
雖然是有東西了,但是我們會希望回傳的是一個完整的 html 頁面,因此我們需要一個 html template,然後在這邊呼叫他。
目前的 note controller 位於 controllers 資料夾底下
在 Phoenix 1.7 之後,note controller 的 html template module 一樣放在 controllers 資料夾底下,名稱則是 note_html.ex
。
我們建立 lib/gratitude_web/controllers/note_html.ex
,並且加入以下程式碼:
defmodule GratitudeWeb.NoteHTML do
use GratitudeWeb, :html
embed_templates "note_html/*"
end
我們在這邊使用 use GratitudeWeb, :html
來引入 Phoenix 的 html 功能。
(與 controller 一樣,如果有興趣的話可以研究一下 use 與 GratitudeWeb 裡面的 html
,但現在可以先忽略。)
embed_templates "note_html/*"
告訴 Phoenix 我們要把 template 放在 note_html
資料夾裡面。一樣,雖然可以自己定義,但是目前我們先依照 Phoenix 預設的規則就可以了。
在 controller 裡面建立 note_html
資料夾,並在裡面新增一個這一次要使用的 template index.html.heex
。
加入以下 html:
<h1>所有的感激筆記</h1>
稍後會在這邊加入筆記列表
我們再回到 note controller 使用 render
來呼叫這個 template:
def index(conn, params) do
render(conn, :index)
end
這個時候再打開同一個頁面,我們就可以看到我們剛剛寫的 html template 了。
我們剛剛從 router 到 controller 到 template 並且成功的顯示出一個 html 頁面,但其實少了從 context 拿到目前所有筆記的部分。
在我們已經先建立好 context 的情況下,我們可以直接呼叫 Gratitude.Notes.list_notes()
來取得所有的筆記。因為我們會在這邊使用多次 Gratitude.Notes
所以可以使用 alias
讓我們不用一直打這麼長的名稱。
在 lib/gratitude_web/controllers/note_controller.ex
修改為:
defmodule GratitudeWeb.NoteController do
use GratitudeWeb, :controller
alias Gratitude.Notes
def index(conn, params) do
notes = Notes.list_notes()
render(conn, :index, notes: notes)
end
end
再使用 Notes.list_notes()
後,我們可以藉由 render
把他帶到 template 裡面。
在 lib/gratitude_web/templates/note_html/index.html.heex
修改為:
<h1>所有的感激筆記</h1>
<%= for note <- @notes do %>
<p><%= note.content %></p>
<% end %>
畫面變成: